package com.specter.mvc.model.batis.plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

import com.specter.mvc.model.Request;
import com.specter.mvc.model.batis.Dialect;
import com.specter.mvc.model.batis.DialectFactory;

import static com.specter.mvc.model.Request.QueryType.*;

import com.specter.utils.JDBCUtils;
import com.specter.utils.StringUtils;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * Note:分页拦截器
 * 
 * @author Liang.Wang
 * @version 2010-11-23 下午02:06:22
 */
@Intercepts({
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }),
		@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class PaginationInterceptor implements Interceptor {

	private String databaseId;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object target = invocation.getTarget();

		// 1.先执行查询总数量（先执行）
		if (target instanceof Executor) {
			MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
			Object parameter = (invocation.getArgs().length > 1) ? invocation.getArgs()[1] : null;

			if (parameter instanceof Request request) {
				if (COUNT == request.getQueryType()) {
					this.count(statement, request); // 统计总量
				}
				if (LIMIT == request.getQueryType()) {
					// do nothing;
				}
				if (LIST == request.getQueryType()) {
					// do nothing;
				}
				if (PAGE == request.getQueryType()) {
					this.count(statement, request);// 统计总量
				}
			}
		}

		// 2.后执行追加分页查询（后执行）
		if (target instanceof StatementHandler statement) {
			Object parameter = statement.getBoundSql().getParameterObject();

			if (parameter instanceof Request request) {
				if (COUNT == request.getQueryType()) {
					// do nothing;
				}
				if (LIMIT == request.getQueryType()) {
					this.limit(statement, request); // 分页查询
				}
				if (LIST == request.getQueryType()) {
					// do nothing;
				}
				if (PAGE == request.getQueryType()) {
					this.limit(statement, request); // 分页查询
				}
			}
		}

		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof Executor || target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		}
		return target;
	}

	@Override
	public void setProperties(Properties prop) {
		System.out.println("");
	}

	public PaginationInterceptor() {
	}

	public PaginationInterceptor(String databaseId) {
		this.databaseId = databaseId;
	}

	/**
	 * 增加总量统计结果
	 * 
	 * @param statement 查询
	 * @param request   参数
	 * @throws Exception 异常
	 */
	private void count(MappedStatement statement, Request request) throws Exception {
		if (request.getRowCount() > 0l) {
			return; // 已经有总条数无需重新统计
		}
		BoundSql boundSql = statement.getBoundSql(request);
		String thisSql = StringUtils.substringBefore(boundSql.getSql().toUpperCase(), "ORDER BY");
		String sql = "SELECT COUNT(1) FROM ( %s ) TOTAL".formatted(thisSql);// 统计语句去掉"order by"以优化性能

		Connection connection = statement.getConfiguration().getEnvironment().getDataSource().getConnection();
		PreparedStatement pstmt = connection.prepareStatement(sql);
		new DefaultParameterHandler(statement, boundSql.getParameterObject(), boundSql).setParameters(pstmt);
		ResultSet rs = pstmt.executeQuery();
		long total = 0;
		if (rs.next()) {
			total = Long.valueOf(rs.getObject(1).toString());
		}
		request.setRowCount(total);// 回写出入的参数
		JDBCUtils.release(connection, pstmt, rs);
	}

	/**
	 * 增加分页查询语句
	 * 
	 * @param statement 查询
	 * @param request   参数
	 * @throws Exception 异常
	 */
	private void limit(StatementHandler statement, Request request) throws Exception {
		String sql = statement.getBoundSql().getSql();
		Dialect dialect = DialectFactory.getDialect(databaseId);
		long limit = request.getLimit(), offset = (request.getPageno() - 1) * limit;
		sql = dialect.limitSQL(sql, offset < 0L ? 0L : offset, limit);

		MetaObject metaStatementHandler = SystemMetaObject.forObject(statement);
		metaStatementHandler.setValue("delegate.boundSql.sql", sql);// 处理后的SQL放回
		metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);// 禁用内存分页
		metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
	}
}
